package com.gitee.wsl.doc.prosemirror.transform.ext

import com.gitee.wsl.doc.prosemirror.model.content.ext.coveredDepths
import com.gitee.wsl.doc.prosemirror.model.fragment.Fragment
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.fragment.ext.closeFragment
import com.gitee.wsl.doc.prosemirror.model.fragment.ext.fitsTrivially
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.ext.insertPoint
import com.gitee.wsl.doc.prosemirror.model.node.ext.resolveSafe
import com.gitee.wsl.doc.prosemirror.model.node.isDefinesContent
import com.gitee.wsl.doc.prosemirror.transform.TransformAble
import com.gitee.wsl.doc.prosemirror.transform.step.ReplaceStep
import kotlin.math.abs


@Suppress("LongMethod", "ComplexMethod")
fun <T: TransformAble> T.replaceRange(from: Int, to: Int, slice: Slice): T? {
    val tr = this
    if (slice.size == 0) return tr.deleteRange(from, to)
    val (_from, _to) = tr.doc.resolveSafe(from, to) ?: return null
    if (slice.fitsTrivially(_from, _to)) {
        return tr.step(ReplaceStep(from, to, slice))
    }

    val targetDepths = coveredDepths(_from, tr.doc.resolve(to)).toMutableList()
    // Can't replace the whole document, so remove 0 if it's present
    if (targetDepths.getOrNull(targetDepths.size - 1) == 0) targetDepths.removeLast()
    // Negative numbers represent not expansion over the whole node at
    // that depth, but replacing from $from.before(-D) to $to.pos.
    var preferredTarget = -(_from.depth + 1)
    targetDepths.add(0, preferredTarget)
    // This loop picks a preferred target depth, if one of the covering
    // depths is not outside of a defining node, and adds negative
    // depths for any depth that has $from at its start and does not
    // cross a defining node.
    var d = _from.depth
    var pos = _from.pos - 1
    while (d > 0) {
        val spec = _from.node(d).type.spec
        if (spec.defining == true || spec.definingAsContext == true || spec.isolating == true) break
        if (targetDepths.indexOf(d) > -1) {
            preferredTarget = d
        } else if (_from.before(d) == pos) {
            targetDepths.add(1, -d)
        }
        d--
        pos--
    }
    // Try to fit each possible depth of the slice into each possible
    // target depth, starting with the preferred depths.
    val preferredTargetIndex = targetDepths.indexOf(preferredTarget)

    val leftNodes = mutableListOf<Node?>()
    var preferredDepth = slice.openStart
    var content = slice.content
    var i = 0
    while (true) {
        val node = content.firstChild
        leftNodes.add(node)
        if (i == slice.openStart) break
        content = node!!.content
        i++
    }

    // Back up preferredDepth to cover defining textblocks directly
    // above it, possibly skipping a non-defining textblock.
    for (d in preferredDepth - 1 downTo 0) {
        val leftNode = leftNodes[d] ?: continue
        val def = leftNode.type.isDefinesContent()
        if (def && !leftNode.sameMarkup(_from.node(abs(preferredTarget) - 1))) {
            preferredDepth = d
        } else if (def || !leftNode.type.isTextblock) {
            break
        }
    }

    for (j in slice.openStart downTo 0) {
        val openDepth = (j + preferredDepth + 1) % (slice.openStart + 1)
        val insert = leftNodes[openDepth] ?: continue
        for (i in targetDepths.indices) {
            // Loop over possible expansion levels, starting with the
            // preferred one
            var targetDepth = targetDepths[(i + preferredTargetIndex) % targetDepths.size]
            var expand = true
            if (targetDepth < 0) {
                expand = false
                targetDepth = -targetDepth
            }
            val parent = _from.node(targetDepth - 1)
            val index = _from.index(targetDepth - 1)
            if (parent.canReplaceWith(index, index, insert.type, insert.marks)) {
                return tr.replace(
                    _from.before(targetDepth),
                    if (expand) _to.after(targetDepth) else to,
                    Slice(
                        slice.content.closeFragment( 0, slice.openStart, openDepth),
                        openDepth,
                        slice.openEnd
                    )
                )
            }
        }
    }

    val startSteps = tr.steps.size
    var from = from
    var to = to
    @Suppress("LoopWithTooManyJumpStatements")
    for (i in targetDepths.size - 1 downTo 0) {
        tr.replace(from, to, slice)
        if (tr.steps.size > startSteps) break
        val depth = targetDepths[i]
        if (depth < 0) continue
        from = _from.before(depth)
        to = _to.after(depth)
    }
    return null
}


fun<T: TransformAble> T.replaceRangeWith(from: Int, to: Int, node: Node)=apply {
    val tr = this
    var from = from
    var to = to
    if (!node.isInline && from == to && tr.doc.resolve(from).parent.content.size != 0) {
        val point = tr.doc.insertPoint(from, node.type)
        if (point != null) {
            to = point
            from = to
        }
    }
    tr.replaceRange(from, to, Slice(Fragment.from(node), 0, 0))
}

fun<T: TransformAble> T.deleteRange( from: Int, to: Int): T{
    val tr = this
    val (_from, _to) = tr.doc.resolveSafe(from, to) ?: return tr
    val covered = coveredDepths(_from, _to)
    for (i in 0 until covered.size) {
        val depth = covered[i]
        val last = i == covered.size - 1
        if ((last && depth == 0) || _from.node(depth).type.contentMatch.validEnd) {
            return tr.delete(_from.start(depth), _to.end(depth))
        }
        if (depth > 0 &&
            (
                    last || _from.node(depth - 1)
                        .canReplace(_from.index(depth - 1), _to.indexAfter(depth - 1))
                    )
        ) {
            return tr.delete(_from.before(depth), _to.after(depth))
        }
    }
    var d = 1

    while (d <= _from.depth && d <= _to.depth) {
        if (
            from - _from.start(d) == _from.depth - d &&
            to > _from.end(d) && _to.end(d) - to != _to.depth - d &&
            _from.start(d - 1) == _to.start(d - 1) &&
            _from.node(d - 1).canReplace(_from.index(d - 1), _to.index(d - 1))
        ) {
            return tr.delete(_from.before(d), to)
        }
        d++
    }
    return tr.delete(from, to)
}
